
#include "rts.h"
#include "pmodel.h"
#include "input.h"
#include "graphics_functions.h"
#include "ai_vehicle.h"
#include "crowd_dynamics.h"

using namespace WtfEngine;
using namespace WtfRts;


/**** Weapon ****/

#define WEAPON_ROTATION_YSLOP	0.1

Weapon::WeaponCreationOptions::WeaponCreationOptions()
: FireRadius(10.0), FireInterval(1.0)
{
};

Weapon::Weapon(const tCreationOptions& opts, const WtfGraphics::GraphicsData::tRef& rGraphics, const tPosition& vRelPos):
GameObject(opts), mPosition(vRelPos), mrGraphics(rGraphics),
mfFireRadius(opts.FireRadius), mfFireInterval(opts.FireInterval), mfLastShotTime(0.0) {
};

bool Weapon::AttackUnit(const WtfEngine::IRtsUnit::tRef& rMyUnit, const WtfEngine::IRtsUnit::tRef& rUnit) {
	tGameTime t = Kernel::GetInstance().GetGameState()->GetTime();
	tGameTime dt = Kernel::GetInstance().GetGameState()->GetDelta();
	tGameTime shotsStart = maxf(mfLastShotTime + mfFireInterval, t - dt);
	tPosition vShotPos;

	// Important that we check line of sight first, so weapon can track the target even when waiting to
	// fire.
	if(HasLineOfSight(rMyUnit, rUnit, vShotPos) && (shotsStart < t)) {
		// Can take another shot
		int iNumShots = ((t - shotsStart) / mfFireInterval) + 1;

		for(int i = 0; i < iNumShots; i++) {
			mrProjectileGenerator->TakeShot(rMyUnit, rUnit, mPosition + rMyUnit->getPosition(), vShotPos);
		};

		mfLastShotTime = shotsStart + (mfFireInterval * (iNumShots - 1));
		return true;
	} else {
		return false;
	};
};

bool Weapon::HasLineOfSight(const WtfEngine::IRtsUnit::tRef& rMyUnit, const WtfEngine::IRtsUnit::tRef& rUnit, tPosition& vHitLocation) {
	// Return true if the other unit is in the attack radius
	return ((rUnit->getPosition() - rMyUnit->getPosition()).length() <= mfFireRadius);
};

void Weapon::Render() {
	WtfGraphics::SaveView();
	WtfGraphics::Translate(mPosition);
	WtfGraphics::Rotate(mOrientation);

	mrGraphics->render();

	WtfGraphics::RestoreView();
};


RayCastWeapon::RayCastWeaponCreationOptions::RayCastWeaponCreationOptions()
: MaxOriX(M_PI / 4), MaxOriY(M_PI), MinOriX(-M_PI / 4), MinOriY(-M_PI),
  RotationSpeedX(M_PI / 6), RotationSpeedY(M_PI / 3)
{
};

RayCastWeapon::RayCastWeapon(const tCreationOptions& opts, const WtfGraphics::GraphicsData::tRef& rGraphics, const tPosition& vRelPos)
: Weapon(opts, rGraphics, vRelPos),
  mfMaxOriX(opts.MaxOriX), mfMaxOriY(opts.MaxOriY), mfMinOriX(opts.MinOriX), mfMinOriY(opts.MinOriY),
  mfRotationSpeedX(opts.RotationSpeedX), mfRotationSpeedY(opts.RotationSpeedY), mfRotationX(0.0), mfRotationY(0.0)
{
};

bool RayCastWeapon::HasLineOfSight(const WtfEngine::IRtsUnit::tRef& rMyUnit, const WtfEngine::IRtsUnit::tRef& rUnit, tPosition& vHitLocation) {
	tDirection vDirection(0, 0, -1);
	vDirection = mOrientation * rMyUnit->getOrientation() * vDirection;
	
	// TODO: what if this is part of a compound weapon?
	WtfEngine::WorldObject::tRef rObject = PhysicsEngine::GetInstance().RayCast(
		tRay(rMyUnit->getPosition() + mPosition, vDirection * mfFireRadius), vHitLocation);
	
	if (!rObject.isNull() && (rUnit == dynamic_cast<WtfEngine::IRtsUnit *>(&*rObject))) {
		return true;
	} else {
		tGameTime dt = Kernel::GetInstance().GetGameState()->GetDelta();

		// Calculate left direction of the weapon, and direction of target
		tDirection vUp(mOrientation * rMyUnit->getOrientation() * tDirection(0, 1, 0));
		tDirection vLeft(vDirection.cross(vUp));
		tDirection vTarget((rUnit->getPosition() - (rMyUnit->getPosition() + mPosition)).normalize());

		// Rotate depending on if the target is left or right of the weapon's diretion
		if(vLeft.dot(vTarget) > WEAPON_ROTATION_YSLOP) {
			// Go left
			mfRotationY -= mfRotationSpeedY * dt;
			if(mfRotationY > M_PI) mfRotationY -= M_PI * 2;
		} else if(vLeft.dot(vTarget) < -WEAPON_ROTATION_YSLOP) {
			// Go right
			mfRotationY += mfRotationSpeedY * dt;
			if(mfRotationY < M_PI) mfRotationY += M_PI * 2;
		} else {
			// Up/down
			if(vUp.dot(vTarget) > 0) {
				mfRotationX += mfRotationSpeedX * dt;
				if(mfRotationX > M_PI) mfRotationX -= M_PI * 2;
			} else if(vUp.dot(vTarget) < 0) {
				mfRotationX -= mfRotationSpeedX * dt;
				if(mfRotationY < M_PI) mfRotationY += M_PI * 2;
			};
		};

		mOrientation = Orientation3D::Rotation(vLeft, mfRotationX) * Orientation3D::Rotation(tVector(0, 1, 0), mfRotationY);
		return false;
	};
};

void InstantDamageProjectile::TakeShot(const WtfEngine::IRtsUnit::tRef &rMyUnit, const WtfEngine::IRtsUnit::tRef &rUnit,
									   const tVector &vFrom, const tVector &vAt)
{
	Unit * pUnit = dynamic_cast<Unit *>(&*rUnit);
	Unit * pMyUnit = dynamic_cast<Unit *>(&*rMyUnit);

	if(pUnit && pMyUnit) {
		if(pUnit->TakeDamage(this, pMyUnit, mfDamage, vAt)) {
			pMyUnit->OnUnitDestroyed(pUnit);
		};
	} else {
		throw new std::logic_error("TakeShot: non RTS unit passed as unit");
	};
};


/**** Unit Selection ****/

#define SELECTIONBOX_DZ				0.5
#define SELECTIONBOX_HEIGHT			2.0
#define SELECTIONBOX_HEIGHT_SLOP	0.2



void UnitSelection::Render() {
	btVector3	aabbMin, aabbMax;
	Unit * pUnit;
	WorldObject * pWorldUnit;

	WtfGraphics::EnableBlending();

	// Render selection box
	if(mbIsSelecting && (mvSelectionEnd != mvSelectionStart)) {
		WtfGraphics::EnableWireframe(0.1);
		WtfGraphics::SaveView();
		
		glColor4f(0.2, 0.2, 0.75, 0.5);
		WtfGraphics::Translate(mvSelectionStart);
		WtfGraphics::DrawBox(tVector(), mvSelectionEnd - mvSelectionStart);

		WtfGraphics::RestoreView();
		WtfGraphics::DisableWireframe();
	};

	// Render unit health bar under mouse (if there is one)
	if(!mrObjectHover.isNull()) {
		pUnit = dynamic_cast<Unit *>(&*mrObjectHover);
		pWorldUnit = dynamic_cast<WorldObject *>(&*mrObjectHover);

		if(pUnit && pWorldUnit) {
			pWorldUnit->RenderStart();
			glTranslatef(0, 3.0, 0);
			pUnit->DisplayHealthBar();
			pWorldUnit->RenderEnd();
		};
	};

	// Display bounding boxes for selected units
	for(tObjectList::iterator i = mObjectList.begin(); i != mObjectList.end(); i++) {
		pUnit = dynamic_cast<Unit *>(&**i);
		pWorldUnit = dynamic_cast<WorldObject *>(&**i);

		if(pUnit && pWorldUnit) {
			pWorldUnit->RenderStart();
			pUnit->DisplayBoundingBox();
			glTranslatef(0, 3.0, 0);
			pUnit->DisplayHealthBar();
			pWorldUnit->RenderEnd();

			pUnit->DisplayTarget();
		};
	};

	WtfGraphics::DisableBlending();
};


void UnitSelection::OnMouseClick() {
	tRay				ray;
	WorldObject::tRef	rObj;
	tVector				vPos;
	MouseUpEvent *	pEv = Kernel::GetInstance().GetCurrentEvent<MouseUpEvent>();

	if(!pEv) return;

	switch(pEv->getButton()) {
		case MOUSE_RIGHT:
			
			//
			// Right mouse button issues actions
			//
			
			ray = Renderer::GetInstance().GetCamera()->PixelToRay(Renderer::GetInstance().GetScreen(),
				iVector2D(pEv->x(), pEv->y()));

			rObj = PhysicsEngine::GetInstance().RayCast(ray, vPos);
			if(!rObj.isNull()) {
				IRtsUnit::tRef rUnit = dynamic_cast<IRtsUnit*>(&*rObj);
				if(!rUnit.isNull()) {
					// Right click on a unit - attack!
					// TODO: check affiliation is enemy.
					for(tObjectList::iterator i = mObjectList.begin(); i != mObjectList.end(); i++) {
						(*i)->AttackOrder(rUnit);
					};
				} else {
					// Set goal position on whole selection
					for(tObjectList::iterator i = mObjectList.begin(); i != mObjectList.end(); i++) {
						(*i)->MoveOrder(vPos);
					};
				};
			};
			break;

		default:
			ObjectSelection::OnMouseClick();
	};
};



/**** AI Models ****/

WtfRts::AiModels::Move::Move(const WtfEngine::StringPool::Entry& sName):
WtfEngine::AiModels::RtsUnit::IntermediateMove(sName, new Behaviours::VehicleMotionControl("ai_vehicleMotionControl"),
											   new Behaviours::CrowdBehaviour("ai_crowdBehaviour"))
{
};

bool WtfRts::AiModels::Move::Evaluate(const WtfEngine::IActor::tRef& rActor) {
	WtfEngine::IMobileRtsUnit * pRtsUnit = dynamic_cast<WtfEngine::IMobileRtsUnit *>(&*rActor);
	if(pRtsUnit) {
		// Stop the unit if it is in range of its goal.
		if((pRtsUnit->getPosition() - pRtsUnit->getGoalPosition()).length() <= pRtsUnit->getGoalRadius()) {
			pRtsUnit->StopOrder();
		} else {
			WtfEngine::AiModels::RtsUnit::IntermediateMove::Evaluate(rActor);
		};
	} else {
		throw new std::logic_error("Non-RtsUnit trying to use RTS move AI model");
	};
};

WtfRts::AiModels::Attack::Attack(const WtfEngine::StringPool::Entry &sName, const WtfEngine::AiModel::tRef &rBaseAiModel)
: WtfEngine::AiModels::RtsUnit::Attack(sName, rBaseAiModel)
{
};



/**** Unit ****/

bool Unit::StopOrder() {
	mrAiModel = new WtfEngine::AiModels::RtsUnit::Idle();
	return true;
};

bool Unit::AttackOrder(const WtfEngine::IRtsUnit::tRef& rUnit) {
	mrTargetedUnit = rUnit;
	// TODO: assumes unit can move and attack...
	//mrAiModel = WtfEngine::DataManager::GetInstance().Load<WtfRts::AiModels::Attack>("ai_rtsunit_attack");
	mrAiModel = new WtfRts::AiModels::Attack("ai_rtsunit_attack", new WtfEngine::AiModels::RtsUnit::Idle());
	return true;
};


bool Unit::TakeDamage(const IProjectile::tRef& rProjectile, const Unit::tRef& rFromUnit, tScalar amt, const tPosition& vAt) {
	mfHealth = maxf(0, mfHealth - amt);
	if(mfHealth == 0) {
		OnDestroy(rProjectile, rFromUnit);
		return true;
	} else {
		return false;
	};
};

void Unit::OnDestroy(const IProjectile::tRef& rProjectile, const Unit::tRef& rFromUnit) {
	// TODO: remove from selection
	//UnitSelection::GetInstance()
};

void Unit::OnUnitDestroyed(const WtfEngine::IRtsUnit::tRef& rUnit) {

};

/**
 * Walks down the unit's list of weapons, attempting to fire at the specified unit.
 **/
bool Unit::AttackUnit(const WtfEngine::IRtsUnit::tRef& rUnit) {
	bool bFired = false;
	for(tWeaponList::iterator i = mWeapons.begin(); i != mWeapons.end(); i++) {
		if((*i)->AttackUnit(this, rUnit))
			bFired = true;
	};
	return bFired;
};

void Unit::RenderLocal() {
	for(tWeaponList::const_iterator i = mWeapons.begin(); i != mWeapons.end(); i++) {
		(*i)->Render();
	};
};

void Unit::Update() {
	// Run the unit behaviour, if unit is alive
	if(mfHealth > 0) {
		mrAiModel->Evaluate(this);
	};
};

void Unit::DisplayBoundingBox() {
	btVector3 min, max;
	WorldObject * pWorldObj = dynamic_cast<WorldObject *>(this);
	pWorldObj->getBody()->getAabb(min, max);
	
	WtfGraphics::SaveView();
	WtfGraphics::EnableWireframe(0.1);

	WtfGraphics::Rotate(pWorldObj->getOrientation().inverse());

	glColor4f(1.0, 1.0, 1.0, 0.75);
	WtfGraphics::DrawBox(TOVECT(min) - pWorldObj->getPosition(), TOVECT(max) - pWorldObj->getPosition());

	WtfGraphics::DisableWireframe();
	WtfGraphics::RestoreView();
};

void Unit::DisplayHealthBar() {
	tScalar fHealthPercentage = (mfHealth / mfMaxHealth);

	fVector4D vColour(0, 1.0, 0, 0.75);
	vColour += fVector4D(1.0, -1.0, 0, 0) * (1.0 - fHealthPercentage);
	glColor4fv(vColour.toArray());
	
	fHealthPercentage /= 2;

	WtfGraphics::DrawBox(tVector(-fHealthPercentage, 0, 0), tVector(fHealthPercentage, 0.2, 0.2));
};

void Unit::DisplayTarget () {
	if(mrAiModel.isNull())
		return;

	if(dynamic_cast<WtfEngine::AiModels::RtsUnit::IntermediateMove *>(&*mrAiModel)) {
		WtfEngine::IMobileRtsUnit * pMobileUnit = dynamic_cast<WtfEngine::IMobileRtsUnit *>(this);
		assert(pMobileUnit);

		tVector vPos, a, b, c, d;

		WtfGraphics::SaveView();
		WtfGraphics::Translate(pMobileUnit->getGoalPosition());
		WtfGraphics::Rotate(Orientation3D::Rotation(tVector(0, 1, 0), Kernel::GetInstance().GetTime()));
		
		a = tVector(-0.2, 2, -0.2);
		b = tVector(0.2, 2, -0.2);
		c = tVector(0.2, 2, 0.2);
		d = tVector(-0.2, 2, 0.2);

		glColor4f(0, 0.75, 0, 0.5);
		WtfGraphics::DrawQuad(a, b, c, d);
		WtfGraphics::DrawTriangle(a, b, vPos);
		WtfGraphics::DrawTriangle(b, c, vPos);
		WtfGraphics::DrawTriangle(c, d, vPos);
		WtfGraphics::DrawTriangle(d, a, vPos);

		WtfGraphics::RestoreView();
	};
};

tScalar Unit::getUnitAffiliation(const WtfEngine::IRtsUnit::tRef &rUnit) const {
	// TODO
	return 0;
};



/**** Vehicle ****/

WtfRts::Vehicle::RtsVehicleCreationOptions::RtsVehicleCreationOptions():
	WtfEngine::Vehicle::tCreationOptions(),
	iNumAxles(2), fWheelRadius(0.4), fAxleSpacing(3.0), fAxleLength(2.25), fSuspensionLength(0.3),
	vFrontAxlePosition(0, -0.3, -1.5), vPhysicalDimensions(2.5, 0.5, 4.0), vCentreOfMass(0, -0.3, 0), fMass(1000)
{
	this->SuspensionStiffness = 20;
};


WtfRts::Vehicle::Vehicle(const tCreationOptions& opts, const WtfGraphics::GraphicsData::tCacheRef& rChassisGraphics):
Unit(), WtfEngine::Vehicle(opts, rChassisGraphics, new PhysicalModels::Oblong("", opts.vPhysicalDimensions, opts.fMass)) {
	
};

bool WtfRts::Vehicle::StopOrder() {
	/// Apply parking brake, reset throttle and steering
	this->setThrottle(0);
	this->setBrake(0);
	this->setSteering(0);
	this->setHandBrake(1.0);
	return Unit::StopOrder();
};

/// Directed movement
bool WtfRts::Vehicle::MoveOrder(const tPosition& vPos) {
	using namespace WtfEngine;

	mvGoalPosition = vPos;
	mrAiModel = DataManager::GetInstance().Load<WtfRts::AiModels::Move>("ai_rtsunit_move");

	return true;
};

void WtfRts::Vehicle::OnDestroy(const IProjectile::tRef& rProjectile, const Unit::tRef& rFromUnit) {
	setBrake(0);
	setThrottle(0);
	setHandBrake(0.2);
	Unit::OnDestroy(rProjectile, rFromUnit);
};

void WtfRts::Vehicle::RenderLocal() {
	WtfEngine::Vehicle::RenderLocal();
	Unit::RenderLocal();
};

void WtfRts::Vehicle::setThrottle(tScalar amt) {
	getBody()->setActivationState(ACTIVE_TAG);
	amt *= 10000;
	mpRaycastVehicle->applyEngineForce(amt, 0);
	mpRaycastVehicle->applyEngineForce(amt, 1);
	mpRaycastVehicle->applyEngineForce(amt, 2);
	mpRaycastVehicle->applyEngineForce(amt, 3);
};

void WtfRts::Vehicle::setBrake(tScalar amt) {
	mpRaycastVehicle->setBrake(amt, 0);
	mpRaycastVehicle->setBrake(amt, 1);
};

void WtfRts::Vehicle::setHandBrake(tScalar amt) {
	mpRaycastVehicle->setBrake(amt, 2);
	mpRaycastVehicle->setBrake(amt, 3);
};

void WtfRts::Vehicle::setSteering(tScalar amt) {
	// Tank steering
	if(amt < 0)
		amt = -sqrt(-amt);
	else
		amt = sqrt(amt);

	amt *= 100000;

	mpRaycastVehicle->applyEngineForce(-amt, 0);
	mpRaycastVehicle->applyEngineForce(amt, 1);
	mpRaycastVehicle->applyEngineForce(-amt, 2);
	mpRaycastVehicle->applyEngineForce(amt, 3);

	//mpRaycastVehicle->setSteeringValue(amt, 0);
	//mpRaycastVehicle->setSteeringValue(amt, 1);
};

void WtfRts::Vehicle::Destroy() {
	Unit::Destroy();
	WtfEngine::Vehicle::Destroy();
};


/**** Ground Unit ****/

GroundUnit::GroundUnitCreationOptions::GroundUnitCreationOptions()
: vDimensions(0.8, 0.4, 2.0), fMass(100) {
};

GroundUnit::GroundUnit(const tCreationOptions& opts, const WtfGraphics::GraphicsData::tCacheRef& rGraphics)
: Physical(opts, rGraphics, new PhysicalModels::Capsule("", opts.vDimensions, opts.fMass))
{
};

void GroundUnit::RenderLocal() {
	Physical::RenderLocal();
	Unit::RenderLocal();
};

void GroundUnit::Destroy() {
	Unit::Destroy();
	WtfEngine::Physical::Destroy();
};

void GroundUnit::OnDestroy(const IProjectile::tRef& rProjectile, const Unit::tRef& rFromUnit) {

};
